#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include "virtual_serial_driver_ioctl.h"

#define CHAR_DEV_MAJOR 256
#define CHAR_DEV_MINOR 0
#define CHAR_DEV_CNT 1
#define CHAR_DEV_NAME "virtual-serial-driver"

struct vser_dev
{
    unsigned int baud;
    struct option opt;
    struct cdev cdev;
};
static struct vser_dev vsdev;

//定义fifo
DEFINE_KFIFO(vsfifo, char, 32);

static int virtual_serial_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int virtual_serial_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t virtual_serial_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    int ret;
    unsigned int copied = 0;

    ret = kfifo_to_user(&vsfifo, buf, count, &copied);

    return ret == 0 ? copied : ret;
}

static ssize_t virtual_serial_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    int ret;
    unsigned int copied = 0;

    ret = kfifo_from_user(&vsfifo, buf, count, &copied);

    return ret == 0 ? copied : ret;
}

static long virtual_serial_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    if (_IOC_TYPE(cmd) != VS_MAGIC)
        return -ENOTTY;

    switch (cmd)
    {
    case VS_SET_BARD:
        vsdev.baud = arg;
        break;
    case VS_GET_BARD:
        arg = vsdev.baud;
        break;
    case VS_SET_FFMT:
        if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
            return -EFAULT;
        break;
    case VS_GET_FFMT:
        if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
            return -EFAULT;
        break;
    default:
        return -ENOTTY;
    }

    return 0;
}

// 用来定义操作字符设备的方法
static struct file_operations vsdev_ops = {
    .owner = THIS_MODULE,                   //只想当前模块
    .open = virtual_serial_open,            //打开
    .release = virtual_serial_release,      //释放
    .read = virtual_serial_read,            //读数据
    .write = virtual_serial_write,          //写数据
    .unlocked_ioctl = virtual_serial_ioctl, //非数据操作ioctl
};

static int __init virtual_serial_driver_init(void)
{
    int ret;
    dev_t dev;

    //合成设备号
    dev = MKDEV(CHAR_DEV_MAJOR, CHAR_DEV_MINOR);

    //注册设备号
    ret = register_chrdev_region(dev, CHAR_DEV_CNT, CHAR_DEV_NAME);
    if (ret)
        goto reg_err;

    //初始化字符设备
    cdev_init(&vsdev.cdev, &vsdev_ops);
    vsdev.cdev.owner = THIS_MODULE;
    vsdev.baud = 115200;
    vsdev.opt.datab = 8;
    vsdev.opt.parity = 0;
    vsdev.opt.stopb = 1;

    //将主设备号对255取余添加到cdev_map中
    ret = cdev_add(&vsdev.cdev, dev, CHAR_DEV_CNT);
    if (ret)
        goto add_err;

    return 0;
add_err:
    unregister_chrdev_region(dev, CHAR_DEV_CNT);
reg_err:
    return ret;
}

static void __exit virtual_serial_driver_exit(void)
{
    dev_t dev;

    //合成设备号
    dev = MKDEV(CHAR_DEV_MAJOR, CHAR_DEV_MINOR);

    //删除cdev
    cdev_del(&vsdev.cdev);

    //注销设备号
    unregister_chrdev_region(dev, CHAR_DEV_CNT);
}

module_init(virtual_serial_driver_init);
module_exit(virtual_serial_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Biven Tang <bivent@163.com>");
MODULE_DESCRIPTION("virtual serial device driver with ioctl!");
MODULE_ALIAS("virtual-serial-driver-ioctl");
